package com.maimao.utils.excel;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.google.common.collect.Lists;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * 参考:https://blog.csdn.net/Violet_201903027/article/details/105724907 进行改造
 *
 * @author wangmaolin
 */
public class ExcelFillCellMergeStrategy implements CellWriteHandler {
    private int[] mergeColumnIndex;
    private int mergeRowIndex;

    /**
     * key:决定是否合并的列 value：由该列决定是否合并的列集合
     *
     * @eg 如共有0-9列，第1到5列是否合并由第0列是否相同决定，第7到9列是否合并由第6列是否相同决定，则map结构为：0->[0,1,2,3,4,5],6->[6,7,8,9],
     * 如果第7-9列也需要由第0列决定，则把7-9额外放到key为0的集合即可
     */
    private Map<Integer, Set<Integer>> mergeUpToMap;

    public ExcelFillCellMergeStrategy() {
    }

    public ExcelFillCellMergeStrategy(int mergeRowIndex, int[] mergeColumnIndex, Map<Integer, Set<Integer>> mergeUpToMap) {
        this.mergeRowIndex = mergeRowIndex;
        this.mergeUpToMap = mergeUpToMap;
        this.mergeColumnIndex = mergeColumnIndex;
    }

    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
        //当前行
        int curRowIndex = cell.getRowIndex();
        //当前列
        int curColIndex = cell.getColumnIndex();

        if (curRowIndex > mergeRowIndex) {
            for (int i = 0; i < mergeColumnIndex.length; i++) {
                if (curColIndex == mergeColumnIndex[i]) {
                    // 当前列需要合并
                    mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);
                    break;
                }
            }
        }
    }

    /**
     * 当前单元格向上合并
     *
     * @param writeSheetHolder
     * @param cell             当前单元格
     * @param curRowIndex      当前行
     * @param curColIndex      当前列
     */
    private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {

        // 首先判断是否需要根据决定列进行合并
        if (mergeUpToMap != null) {
            // 决定当前列是否需要行合并的所有列集合
            List<Integer> upToColList = Lists.newArrayListWithCapacity(mergeUpToMap.size());
            for (Map.Entry<Integer, Set<Integer>> entry : mergeUpToMap.entrySet()) {
                if (entry.getValue().contains(curColIndex)) {
                    upToColList.add(entry.getKey());
                }
            }
            if (ObjectUtil.isEmpty(upToColList)) {
                throw new RuntimeException("合并列失败");
            }
            boolean needMerge = true;
            for (Integer upToCol : upToColList) {

                //判断每个决定列是否满足合并条件【决定列当前行值与决定列上一行值相同或上一行为空时进行合并，否则不合并】
                Cell upToCell = cell.getSheet().getRow(curRowIndex).getCell(upToCol);
                Object upToCellValue = getCellValue(upToCell);
                Cell uptoCellPre = cell.getSheet().getRow(curRowIndex - 1).getCell(upToCol);
                if (uptoCellPre != null && !Objects.equals(getCellValue(uptoCellPre), upToCellValue)) {
                    //不需要合并
                    needMerge = false;
                }
            }
            if (needMerge) {
                //所有决定列的值都相同才进行合并 这样可以避免 二级决定列值相同但是一级决定列值不同的行单元格合并
                doMerge(writeSheetHolder, cell, curRowIndex, curColIndex);

            }

        } else {
            //否则按照默认规则合并，只要同列上下行值相同就都合并
            doMerge(writeSheetHolder, cell, curRowIndex, curColIndex);

        }


    }

    private void doMerge(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
        //决定性单元格和上一行对应列的值相等，则进行进一步是否合并判断
        Object curData = getCellValue(cell);
        Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);
        Object preData = getCellValue(preCell);

        // 比较当前行的第一列的单元格与上一行是否相同，相同合并当前单元格与上一行
        if (curData == null || preData == null) {
            return;
        }
        if (curData.equals(preData)) {
            Sheet sheet = writeSheetHolder.getSheet();
            List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
            boolean isMerged = false;
            for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
                CellRangeAddress cellRangeAddr = mergeRegions.get(i);
                // 若上一个单元格已经被合并，则先移出原有的合并单元，再重新添加合并单元
                if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
                    sheet.removeMergedRegion(i);
                    cellRangeAddr.setLastRow(curRowIndex);
                    sheet.addMergedRegion(cellRangeAddr);
                    isMerged = true;
                }
            }
            // 若上一个单元格未被合并，则新增合并单元
            if (!isMerged) {
                CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
                sheet.addMergedRegion(cellRangeAddress);
            }
        }
    }

    private static Object getCellValue(Cell cell) {
        switch (cell.getCellTypeEnum()) {
            case BOOLEAN:
                return CellValue.valueOf(cell.getBooleanCellValue()).formatAsString();
            case ERROR:
                return CellValue.getError(cell.getErrorCellValue()).formatAsString();
            case NUMERIC:
                double numericCellValue = cell.getNumericCellValue();
                if (DateUtil.isCellDateFormatted(cell)) {
                    return DateUtil.getJavaDate(numericCellValue);
                } else {
                    return numericCellValue;
                }
            case STRING:
                return cell.getRichStringCellValue().getString();
            default:
                return null;
        }
    }

    public int[] getMergeColumnIndex() {
        return mergeColumnIndex;
    }

    public void setMergeColumnIndex(int[] mergeColumnIndex) {
        this.mergeColumnIndex = mergeColumnIndex;
    }

    public int getMergeRowIndex() {
        return mergeRowIndex;
    }

    public void setMergeRowIndex(int mergeRowIndex) {
        this.mergeRowIndex = mergeRowIndex;
    }
}
